/**
 * 
 */
package upm.pb4agent.team2.environment;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Date;
import java.util.Vector;

import jade.content.AgentAction;
import jade.content.Concept;
import jade.content.ContentElement;
import jade.content.lang.Codec.CodecException;
import jade.content.onto.OntologyException;
import jade.content.onto.basic.Action;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.SimpleBehaviour;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import upm.pb4agent.ontology.Dock;
import upm.pb4agent.ontology.EnteringDock;
import upm.pb4agent.ontology.LeavingDock;
import upm.pb4agent.ontology.RaceFinished;
import upm.pb4agent.ontology.RaceStarted;
import upm.pb4agent.ontology.SubscribeReferee;
import upm.pb4agent.team2.Constants;
import upm.pb4agent.team2.Team2BaseAgent;
import upm.pb4agent.team2.internalOntology.InternalEnteringDock;
import upm.pb4agent.team2.internalOntology.InternalLeavingDock;
import upm.pb4agent.team2.internalOntology.StartRacePlease;

//imports used by SubscriptionInitiator
/*
import jade.proto.SubscriptionResponder;
import jade.domain.FIPANames;
 */

/**
 * Referee Agent
 * 
 * @version 1.0
 * @author Alejandro Bustamante
 */
public class Referee extends Team2BaseAgent 
{
	private static final long serialVersionUID = 3842220818303216425L;
	private Vector<AID> subscribedAgents = new Vector<AID>();
	
	private int numberOfBoatsRacing = 0;
	private int numberOfBoatsFinished = 0;
	
	private Boolean allowStart = false;
	private Long periodBeforeStartingRace;
	private Long registrationPeriodFinishTime;
	
	private long msTime;
	private long msBegin;
	private long msEnd;

	protected void setup() 
	{
		super.setup();

		registerOnDF(Constants.REFEREE);
		
		try 
		{
			periodBeforeStartingRace = Long.valueOf(getPropertyFromConfigFile("starting_race_period"));
		}
		catch (NumberFormatException e) 
		{
			e.printStackTrace();
		} 
		catch (FileNotFoundException e) 
		{
			e.printStackTrace();
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
		
		addBehaviour(new ProcessSubscription(this));
		addBehaviour(new CallStartOfRace(this));
		
		// Using SubscriptionResponder:
		//addBehaviour(new RefereeResponder(this));
	}
	
	// -------------------------------------------
	// --------------- METHODS -------------------
	// -------------------------------------------

	private String resultToPrint =  "==================================================" +
									"\n------------------ Race Results ------------------" +
									"\n==================================================" +
									"\nPosition	Boat		Time (seconds)";
	
	private ACLMessage handleRequest(ACLMessage aclMessage)
	{
		ACLMessage reply = aclMessage.createReply();
		try 
		{			
			// Check if the message is a subscription request
			if (aclMessage.getPerformative() == ACLMessage.SUBSCRIBE) 
			{
				// Handle the subscription request
				try 
				{
					ContentElement contentElement = null;
					contentElement = getContentManager().extractContent(aclMessage);
					
					if (contentElement instanceof AgentAction)
					{
						Action action = (Action) contentElement;
						Concept concept = action.getAction();
						
						if (concept instanceof SubscribeReferee)
						{
							this.subscribeRequest(aclMessage);
							
							reply.setPerformative(ACLMessage.AGREE);
						}
					}
				} 
				catch (CodecException e) 
				{
					e.printStackTrace();
				}
				
			} 
			else if (aclMessage.getPerformative() == ACLMessage.INFORM)
			{
				if (aclMessage.getContent() != null)
				{
					try 
					{
						ContentElement contentElement = null;
						contentElement = getContentManager().extractContent(aclMessage);
						
						if (contentElement instanceof AgentAction)
						{
							Action action = (Action) contentElement;
							Concept concept = action.getAction();
							
							if (concept instanceof StartRacePlease)
							{
								numberOfBoatsRacing = ((StartRacePlease)concept).getNumberOfBoatsRacing();
								
								registrationPeriodFinishTime = new Date().getTime();
								this.allowStart = true;
							}
							
							else if (concept instanceof InternalEnteringDock)
							{
								if (!((InternalEnteringDock)concept).getDockName().equals(Constants.FINISH_LINE))
								{
									EnteringDock enteringDock = new EnteringDock();
									enteringDock.setBoatAID(((InternalEnteringDock)concept).getBoatAID());
									
									Dock dock = new Dock();
									dock.setName(((InternalEnteringDock)concept).getDockName());
									
									enteringDock.setDock(dock);
									enteringDock.setPosition(getPosition(dock.getName()));
									enteringDock.setTimeStamp(String.valueOf(new Date().getTime()));
									
									printOut(enteringDock.getBoatAID().getLocalName()+" EnteringDock "+enteringDock.getDock().getName()+" being notified now.");
									
									createACLMessageAndNotify(this.getAID(), ACLMessage.INFORM, new Action(this.getAID(), enteringDock), EnteringDock.class.getSimpleName().toString());
								}
								else
								{
									msEnd = System.currentTimeMillis();
									msTime += msEnd-msBegin;
									
									double seconds = msTime/1000;
									
									printOut("------------------------ "+((InternalEnteringDock)concept).getBoatAID().getLocalName()+" crossed the Finish Line!! Race time: "+seconds+" seconds. Congratulations!! ------------------------");
									
									RaceFinished raceFinished = new RaceFinished();
									raceFinished.setBoatAID(((InternalEnteringDock)concept).getBoatAID());
									raceFinished.setPosition(getPosition(Constants.FINISH_LINE));
									raceFinished.setTimeStamp(String.valueOf(new Date().getTime()));
									
									createACLMessageAndNotify(this.getAID(), ACLMessage.INFORM, new Action(this.getAID(), raceFinished), RaceFinished.class.getSimpleName().toString());

									numberOfBoatsFinished ++;
									
									resultToPrint = resultToPrint + "\n  "+numberOfBoatsFinished+". 		"+raceFinished.getBoatAID().getLocalName()+" 		"+seconds;
									
									if (numberOfBoatsFinished == numberOfBoatsRacing)
									{
										resultToPrint = resultToPrint + "\n==================================================";
										
										System.out.println(resultToPrint);
									}
								}
								
								reply = null;
							}
							
							else if (concept instanceof InternalLeavingDock)
							{
								LeavingDock leavingDock = new LeavingDock();
								leavingDock.setBoatAID(((InternalLeavingDock)concept).getBoatAID());
								
								Dock dock = new Dock();
								dock.setName(((InternalLeavingDock)concept).getDockName());
								
								leavingDock.setDock(dock);
								
								createACLMessageAndNotify(this.getAID(), ACLMessage.INFORM, new Action(this.getAID(), leavingDock), LeavingDock.class.getSimpleName().toString());
								
								reply = null;
							}
							
						}
					} 
					catch (CodecException e) 
					{
						e.printStackTrace();
					}
				}
			}
			else if (aclMessage.getPerformative() == ACLMessage.CANCEL)
			{
				this.cancelSubscription(aclMessage);
			}
			else
			{
				// Message performative is wrong
				reply.setPerformative(ACLMessage.NOT_UNDERSTOOD);
			}
		} 
		catch (OntologyException oe) 
		{
			oe.printStackTrace();
		}
		
		return reply;
	}
	
	private int bDockCounter = 0;
	private int cDockCounter = 0;
	private int dDockCounter = 0;
	private int finishLineCounter = 0;
	
	private int getPosition(String dockName)
	{
		int position = 0;
		
		if (dockName.equals(Constants.DOCK_B_ID))
		{
			bDockCounter ++;
			position = bDockCounter;
		}
		else if (dockName.equals(Constants.DOCK_C_ID))
		{
			cDockCounter ++;
			position = cDockCounter;
		}
		else if (dockName.equals(Constants.DOCK_D_ID))
		{
			dDockCounter ++;
			position = dDockCounter;
		}
		else if (dockName.equals(Constants.FINISH_LINE))
		{
			finishLineCounter ++;
			position = finishLineCounter;
		}
		
		return position;
	}
	
	private void subscribeRequest(ACLMessage aclMessage)
	{
		AID aID = aclMessage.getSender(); 
		
		if (!subscribedAgents.contains(aID))
		{	
			subscribedAgents.add(aID);
		}
	    //safer to use getSender() - see above.
		/*Iterator itr = aclMessage.getAllReplyTo();
		while(itr.hasNext()) {

		    AID aID = (AID) itr.next(); 
		    subscribedAgents.add(aID);
		} */
	}
	
	private void cancelSubscription(ACLMessage aclMessage)
	{
		AID aID = aclMessage.getSender(); 
		
		if (subscribedAgents.contains(aID))
		{
			subscribedAgents.removeElement(aID);
		}
	}
	
	private void createACLMessageAndNotify(AID aid, int performative, Action action, String protocol)
	{
		ACLMessage aclMessage = createACLMessage(performative);
		aclMessage.setProtocol(protocol);
		
		try 
		{
			getContentManager().fillContent(aclMessage, action);
		} 
		catch (CodecException ce) 
		{
			ce.printStackTrace();
		} 
		catch (OntologyException oe) 
		{
			oe.printStackTrace();
		}

		notifyToSubscribed(aclMessage);
	}
	
	private void notifyToSubscribed(ACLMessage aclMessage)
	{
		//Collections.shuffle(subscribedAgents);
		for (AID aID : subscribedAgents)
		{
			aclMessage.addReceiver(aID);
		}
		
		send(aclMessage);
	}

	// -------------------------------------------
	// --------------- CLASSES -------------------
	// -------------------------------------------
	
	class ProcessSubscription extends CyclicBehaviour 
	{
		private static final long serialVersionUID = 3040576593439520044L;

		public ProcessSubscription(Agent agent) 
		{
			super(agent);
		}

		@Override
		public void action() 
		{
			ACLMessage aclMessage = receive(MessageTemplate.and(MessageTemplate.MatchLanguage(codec.getName()), MessageTemplate.or(MessageTemplate.MatchOntology(ontology.getName()), MessageTemplate.MatchOntology(internalOntology.getName()))));

			printReceivedProtocol(aclMessage);
			
			if (aclMessage != null) 
			{
				ACLMessage reply = handleRequest(aclMessage);
				
				if (reply != null)
				{
					myAgent.send(reply);
				}
			} 
			else 
			{
				// No message yet
				block();
			}
		}

	}
	
	class CallStartOfRace extends SimpleBehaviour 
	{
		private static final long serialVersionUID = -5078361275218695197L;
		private boolean startRaceCalled = false;
		
		public CallStartOfRace(Agent agent) 
		{
			super(agent);
		}
		
		@Override
		public void action() 
		{
			Long currentTime = new Date().getTime();
			
			if (allowStart && (currentTime - registrationPeriodFinishTime >= periodBeforeStartingRace)) 
			{
				printOut("Start Race NOW!");
				
				RaceStarted startRaceSignal = new RaceStarted();
				
				Action action = new Action(myAgent.getAID(), startRaceSignal);
				
				createACLMessageAndNotify(myAgent.getAID(), ACLMessage.INFORM, action, RaceStarted.class.getSimpleName().toString());
				
				msTime = 0;
				msBegin = System.currentTimeMillis();
				
				startRaceCalled = true;
			} 
		}

		@Override
		public boolean done() 
		{
			return startRaceCalled;
		}
		
	}
	
	// ----------------------------------------------------------------------------------------------------------------------
	// -------- SUBSCRIPTION RESPONDER : USE THIS BEHAVIOUR TO RESPOND TO SUBSCRIPTIONS USING SUBSCRIPTION RESPONDER --------
	// ----------------------------------------------------------------------------------------------------------------------

	/*
	class RefereeResponder extends SubscriptionResponder 
	{ 
		private static final long serialVersionUID = -2580756409623774433L;

		RefereeResponder(Agent a) 
        { 
            super(a, MessageTemplate.and(MessageTemplate.or(MessageTemplate.MatchPerformative(ACLMessage.SUBSCRIBE), MessageTemplate.MatchPerformative(ACLMessage.CANCEL)), MessageTemplate.MatchProtocol(FIPANames.InteractionProtocol.FIPA_SUBSCRIBE))); 
        } 
        
        protected ACLMessage handleSubscription(ACLMessage subscription_msg) 
        { 
        	ACLMessage reply = subscription_msg.createReply();
        	reply.setPerformative(ACLMessage.REFUSE);
        	
        	// if subscription is ok, create it 
            
        	//printOut("*********"+ subscription_msg.getContent()+"****");
            //printOut("*********"+ Constants.REFEREE_SUBSCRIPTION+"****");
            //printOut("*********"+subscription_msg.getContent().trim()+"****");
        	//weird shit, this if fails!
            //if (subscription_msg.getContent().trim() == Constants.REFEREE_SUBSCRIPTION)
            {
        		createSubscription(subscription_msg); 
        		
        		reply.setPerformative(ACLMessage.AGREE);
            }
            
            // if successful, should answer (return) with AGREE; otherwise with REFUSE or NOT_UNDERSTOOD 
            
            return reply;
        } 
        
        protected void notify(ACLMessage inform) 
        { 
        	printOut("DEBUG: message received at Referee.notify()");
            // this is the method you invoke ("call-back") for creating a new inform message; 
            // it is not part of the SubscriptionResponder API, so rename it as you like 
          
            // go through every subscription 
            //Vector<SubscriptionResponder.Subscription> subs = getSubscriptions(); // this doesnt work
        	Vector subs = getSubscriptions();
        	for(int i=0; i < subs.size(); i++) 
            {
                ((SubscriptionResponder.Subscription) subs.elementAt(i)).notify(inform); 
            }
       } 
    }*/

}
